const canvas = document.querySelectorAll("canvas");
const cts = [];

let source = null,
  w = 0,
  h = 0,
  t = 0,
  csi = 0,
  _min = 0;

const HEX_CRAD = 32,
  HEX_HLW = 2,
  HEX_GAP = 4,
  T_SWITCH = 64,
  HEX_BG = "#171717",
  HEX_HL = "#2A2A2A",
  NEON_PALETE = [
    "#cb3301",
    "#ff0066",
    "#ff6666",
    "#feff99",
    "#ffff67",
    "#ccff66",
    "#99fe00",
    "#fe99ff",
    "#ff99cb",
    "#fe349a",
    "#cc99fe",
    "#6599ff",
    "#00ccff",
    "#ffffff",
  ];

const wp = NEON_PALETE.map((c) => {
  const num = parseInt(c.replace("#", ""), 16);
  return {
    r: (num >> 16) & 0xff,
    g: (num >> 8) & 0xff,
    b: num & 0xff,
  };
});

function fillBg(bg_fill) {
  cts[0].fillStyle = bg_fill;
  cts[0].beginPath();
  cts[0].rect(0, 0, w, h);
  cts[0].closePath();
  cts[0].fill();
}

function neon() {
  const f = 1 / T_SWITCH;
  const k = (t % T_SWITCH) * f;
  const rgb = {
    r: ~~(wp[csi].r * (1 - k) + wp[(csi + 1) % wp.length].r * k),
    g: ~~(wp[csi].g * (1 - k) + wp[(csi + 1) % wp.length].g * k),
    b: ~~(wp[csi].b * (1 - k) + wp[(csi + 1) % wp.length].b * k),
  };
  const rgb_str = `rgb(${rgb.r},${rgb.g},${rgb.b})`;
  const light = cts[0].createRadialGradient(
    source.x,
    source.y,
    0,
    source.x,
    source.y,
    0.875 * _min
  );
  const stp =
    0.5 - 0.5 * Math.sin(7 * t * f) * Math.cos(5 * t * f) * Math.sin(3 * t * f);
  light.addColorStop(0, rgb_str);
  light.addColorStop(stp, "rgba(0,0,0,.03");

  fillBg("rgba(0,0,0,.02");
  fillBg(light);
  t++;
  if (t % T_SWITCH === 0) {
    csi++;
    if (csi === wp.length) {
      csi = 0;
      t = 0;
    }
  }
  requestAnimationFrame(neon);
}

const unit_x = 3 * HEX_CRAD + HEX_GAP * Math.sqrt(3),
  unit_y = HEX_CRAD * Math.sqrt(3) * 0.5 + 0.5 * HEX_GAP,
  off_x = 1.5 * HEX_CRAD + 0.5 * HEX_GAP * Math.sqrt(3);

class GridItem {
  constructor(x, y) {
    this.x = x || 0;
    this.y = y || 0;
    this.points = { hex: [], hl: [] };
    this.init();
  }

  init() {
    let x,
      y,
      a,
      ba = Math.PI / 3,
      ri = HEX_CRAD - 0.5 * HEX_HLW;
    for (let i = 0; i < 6; i++) {
      a = i * ba;
      x = this.x + HEX_CRAD * Math.cos(a);
      y = this.y + HEX_CRAD * Math.sin(a);
      this.points.hex.push({ x, y });
      if (i > 2) {
        x = this.x + ri * Math.cos(a);
        y = this.y + ri * Math.sin(a);
        this.points.hl.push({ x, y });
      }
    }
  }

  draw(ct) {
    for (let i = 0; i < 6; i++) {
      ct[(i === 0 ? "move" : "line") + "To"](
        this.points.hex[i].x,
        this.points.hex[i].y
      );
    }
  }

  highlight(ct) {
    for (let i = 0; i < 3; i++) {
      ct[(i === 0 ? "move" : "line") + "To"](
        this.points.hl[i].x,
        this.points.hl[i].y
      );
    }
  }
}

class Grid {
  constructor(rows, cols) {
    this.rows = rows || 16;
    this.cols = cols || 16;
    this.items = [];
    this.n = this.items.length;
    this.init();
  }
  //   get n() {
  //     return this.items.length;
  //   }

  init() {
    let x, y;
    for (let r = 0; r < this.rows; r++) {
      y = r * unit_y;
      for (let c = 0; c < this.cols; c++) {
        x = (r % 2 == 0 ? 0 : off_x) + c * unit_x;
        this.items.push(new GridItem(x, y));
      }
    }
    this.n = this.items.length;
  }

  draw(ct) {
    ct.fillStyle = HEX_BG;
    ct.beginPath();
    for (let i = 0; i < this.n; i++) {
      this.items[i].draw(ct);
    }
    ct.closePath();
    ct.fill();

    ct.strokeStyle = HEX_HL;
    ct.beginPath();
    for (let i = 0; i < this.n; i++) {
      this.items[i].highlight(ct);
    }
    ct.closePath();
    ct.stroke();
  }
}

const init = () => {
  const s = getComputedStyle(canvas[0]);

  w = ~~s.width?.split("px")[0];
  h = ~~s.height?.split("px")[0];
  _min = 0.75 * Math.min(w, h);

  for (let i = 0; i < canvas.length; i++) {
    canvas[i].width = w;
    canvas[i].height = h;
    cts[i] = canvas[i].getContext("2d");
  }

  const rows = ~~(h / unit_y) + 2,
    cols = ~~(w / unit_x) + 2;

  const grid = new Grid(rows, cols);
  grid.draw(cts[1]);

  if (!source) {
    source = { x: ~~(w / 2), y: ~~(h / 2) };
  }

  neon();
};

init();

addEventListener("resize", init, false);
addEventListener("mousemove", (e) => {
  source = { x: e.clientX, y: e.clientY };
});
